package ca.concordia.pga.main;

import java.util.HashSet;
import java.util.Map;

import ca.concordia.pga.models.Concept;
import ca.concordia.pga.models.PlanningGraph;
import ca.concordia.pga.models.Service;
import ca.concordia.pga.utils.LookAhead;

public class CompositionLogic {

	private boolean solved = false;
	private long startTime;
	private HashSet<Service> newALevelServices;
	private HashSet<Concept> newPlevelConcepts;
	private Map<String, Service> serviceMap;
	private PlanningGraph pg;

	public CompositionLogic(PlanningGraph pg,  Map<String, Service> serviceMap) {
		super();		
		this.serviceMap = serviceMap;
		this.pg = pg;
		this.pg.setOriginalConceptSet(pg.getPLevel(pg.getPLevels().size()-1));
		System.out.println("************ This forward planning graph is shown as follows**************");
		System.out.print("Input concepts: ");
		for (Concept c : pg.getOriginalConceptSet())
			System.out.print(c.getName()+", ");
		System.out.print("\nGoal concepts: ");
		for (Concept c : pg.getGoalSet())
			System.out.print(c.getName()+", ");
	}

	public void start() {
		startTime = System.currentTimeMillis();
		int level=1;
		while(!solved) {
			updateGiven();
			LookAhead.lookAhead(serviceMap, (HashSet<Concept>) pg.getGivenConceptSet());
			newPlevelConcepts = new HashSet<Concept>();
			newALevelServices = new HashSet<Service>();
			callAvailableServices();
			pg.addALevel(newALevelServices);
			pg.addPLevel(newPlevelConcepts);
			addSolutions(serviceMap);
			checkSolved();
			System.out.print("\n\n************Level "+level+"*****************");
			System.out.print("\nServices: ");
			for (Service s : newALevelServices)
				System.out.print(s.getName()+", ");
			System.out.print("\nNumber of services: "+newALevelServices.size());
			System.out.print("\nNumber of concepts: "+newPlevelConcepts.size());
			level++;
		}
		int invoked=0;
		for (int i=0;i<pg.getALevels().size();++i)
			invoked+=pg.getALevel(i).size();
		System.out.println("\n\n================================");
		System.out.println("Forward search time: "+(System.currentTimeMillis()-startTime)+"ms");
		System.out.println("Backtracking Time: "+LookAhead.lookAheadTime+"ms"); 
		System.out.println("Invoked service: "+invoked);
		System.out.println("Levels: "+(pg.getALevels().size()-1));
		System.out.print("==================================");
	}

	private void updateGiven() {
		//put p levels into given
		for(Concept c : pg.getPLevel(pg.getPLevels().size()-1)) {
			pg.getGivenConceptSet().add(c);
		}
	}

	private void callAvailableServices() {
		for(Service s : serviceMap.values()) {
			if(pg.getGivenConceptSet().containsAll(s.getInputConceptSet())&&!s.isSolutionService()) {
				expandService(s);
			}
		}
	}

	private void expandService(Service s) {
		//get the return type set of Concepts
		HashSet<Concept> outputConcepts = (HashSet<Concept>)s.getOutputConceptSet();
		HashSet<Concept> inputConcepts = (HashSet<Concept>)s.getInputConceptSet();

		//add to the P Levels
		for(Concept c : outputConcepts) {
			if (!c.isRin()) {
				newPlevelConcepts.add(c);
				c.addServiceToOrigin(s);
				for(Concept input : inputConcepts) {
					for(Service inputService : input.getOriginServiceSet()) {
						c.addServiceToOrigin(inputService);
					}
				}
				c.setRin(true);
			}
			if(pg.getGoalSet().contains(c)) {
				c.addGoal(s);
				c.setGoal(true);
			}
		}

		for(Concept c : inputConcepts) {
			c.addUsedByService(s);
			for (Concept con : s.getOutputConceptSet()) {
				c.addConceptToChildrenIndex(con);
			}
		}
		newALevelServices.add(s);
	}

	private void addSolutions(Map<String, Service> serviceMap) {
		for(Service s : newALevelServices) {
			serviceMap.get(s.getName()).setSolutionService(true);
		}
	}

	private void checkSolved() {
		boolean isSolved = true;
		for(Concept c : pg.getGoalSet()) {
			if(!c.isGoal()) {
				isSolved = false;
			}
		}

		solved = isSolved;
	}

}
